﻿using System;
using System.Collections.Generic;

namespace Core.FSM
{
    internal delegate TF FsmStateDelegate<in T, TF>(T signal, TF currentValue);

    internal delegate void FsmUndefinedStateDelegate<in T>(T signal);

    internal class FSMState<T>
    {
        private readonly string m_label;
        private readonly Dictionary<T, FSMState<T>> m_signals;

        public FSMState(string label)
        {
            m_label = label;
            m_signals = new Dictionary<T, FSMState<T>>();
        }

        public void AddTransition(List<T> signals, FSMState<T> state)
        {
            foreach (T signal in signals)
            {
                if (!m_signals.ContainsKey(signal))
                {
                    m_signals.Add(signal, state);
                }
            }
        }

        public FSMState<T> MakeTransition(T signal)
        {
            if (m_signals.ContainsKey(signal))
            {
                return m_signals[signal];
            }
            return null;
        }

        public override string ToString()
        {
            return m_label;
        }
    }

    internal class FiniteStateMachine<T, TF>
    {
        private readonly FSMState<T> m_defaultState;
        private readonly Dictionary<FSMState<T>, FsmStateDelegate<T, TF>> m_statesCallbacks;
        private readonly FsmUndefinedStateDelegate<T> m_undefStateCallback;
        private FSMState<T> m_currentState;
        private TF m_fsmValue;

        public FiniteStateMachine(string defaultStateLabel, FsmStateDelegate<T, TF> defaultStateCallback, FsmUndefinedStateDelegate<T> undefStateCallback)
        {
            m_statesCallbacks = new Dictionary<FSMState<T>, FsmStateDelegate<T, TF>>();
            m_currentState = AddState(defaultStateLabel, defaultStateCallback);
            m_defaultState = m_currentState;
            m_undefStateCallback = undefStateCallback;
        }

        public FSMState<T> DefaultState
        {
            get { return m_defaultState; }
        }

        public TF FSMValue
        {
            get { return m_fsmValue; }
            set { m_fsmValue = value; }
        }

        public bool Signal(T signal)
        {
            FSMState<T> temp = m_currentState.MakeTransition(signal);
            if (temp == null)
            {
                m_undefStateCallback(signal);
                throw new Exception("Undefined state exception");
            }
            m_currentState = temp;
            if (m_statesCallbacks[m_currentState] != null)
            {
                m_fsmValue = m_statesCallbacks[m_currentState](signal, m_fsmValue);
            }
            return m_currentState == m_defaultState;
        }

        public FSMState<T> AddState(string label, FsmStateDelegate<T, TF> callback)
        {
            var adding = new FSMState<T>(label);
            m_statesCallbacks.Add(adding, callback);
            return adding;
        }
    }
}